---@type QuestieFramePool
local QuestieFramePool = QuestieLoader:ImportModule("QuestieFramePool")
---@type QuestieMap
local QuestieMap = QuestieLoader:ImportModule("QuestieMap")
---@type QuestieDBMIntegration
local QuestieDBMIntegration = QuestieLoader:ImportModule("QuestieDBMIntegration")
---@type QuestieDB
local QuestieDB = QuestieLoader:ImportModule("QuestieDB")
---@type DailyQuests
local DailyQuests = QuestieLoader:ImportModule("DailyQuests")
---@type QuestieLink
local QuestieLink = QuestieLoader:ImportModule("QuestieLink")
---@type QuestieQuest
local QuestieQuest = QuestieLoader:ImportModule("QuestieQuest")

--- COMPATIBILITY ---
local C_Map = QuestieCompat.C_Map
local WorldMapFrame = QuestieCompat.WorldMapFrame

local HBDPins = QuestieCompat.HBDPins or LibStub("HereBeDragonsQuestie-Pins-2.0")

QuestieFramePool.Qframe = {}

local _Qframe = {}

---@return IconFrame
function QuestieFramePool.Qframe:New(frameId, OnEnter)
    ---@class IconFrame : Button
    local newFrame = CreateFrame("Button", "QuestieFrame" .. frameId)
    newFrame.frameId = frameId;

    -- Add the frames to the ignore list of the Minimap Button Bag (MBB) addon
    -- This is quite ugly but the only thing we can do currently from our side
    -- Check #1504
    if MBB_Ignore then
        tinsert(MBB_Ignore, newFrame:GetName())
    end
    newFrame.isSkinned = true -- prevents ElvUI_Enhanced_MinimapButtonGrabber from hidding our pins

    if frameId > 5000 then
        Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieFramePool] Over 5000 frames... maybe there is a leak?", frameId)
    end

    newFrame.glow = CreateFrame("Button", "QuestieFrame" .. frameId .. "Glow", newFrame) -- glow frame
    newFrame.glow:SetFrameStrata("FULLSCREEN");
    newFrame.glow:SetWidth(18)                                                           -- Set these to whatever height/width is needed
    newFrame.glow:SetHeight(18)


    newFrame:SetFrameStrata("FULLSCREEN");
    newFrame:SetWidth(16)  -- Set these to whatever height/width is needed
    newFrame:SetHeight(16) -- for your Texture
    newFrame:SetPoint("CENTER", -8, -8)
    newFrame:EnableMouse(true)

    local newTexture = newFrame:CreateTexture(nil, "OVERLAY", nil, 0)
    --t:SetTexture("Interface\\Icons\\INV_Misc_Eye_02.blp")
    --t:SetTexture("Interface\\Addons\\!Questie\\Icons\\available.blp")
    newTexture:SetWidth(16)
    newTexture:SetHeight(16)
    newTexture:SetAllPoints(newFrame)

    if not QuestieCompat.Is335 then
        newTexture:SetTexelSnappingBias(0)
        newTexture:SetSnapToPixelGrid(false)
    end

    local glowt = newFrame.glow:CreateTexture(nil, "OVERLAY", nil, -1)
    glowt:SetWidth(18)
    glowt:SetHeight(18)
    glowt:SetAllPoints(newFrame.glow)

    ---@class IconTexture : Texture
    newFrame.texture = newTexture;
    newFrame.texture.OLDSetVertexColor = newFrame.texture.SetVertexColor;
    function newFrame.texture:SetVertexColor(r, g, b, a)
        self:OLDSetVertexColor(r, g, b, a);
        --We save the colors to the texture object, this way we don't need to use GetVertexColor
        self.r = r or 1;
        self.g = g or 1;
        self.b = b or 1;
        self.a = a or 1;
    end

    --We save the colors to the texture object, this way we don't need to use GetVertexColor
    newFrame.texture:SetVertexColor(1, 1, 1, 1);

    newFrame.glowTexture = glowt
    newFrame.glowTexture.OLDSetVertexColor = newFrame.glowTexture.SetVertexColor;
    function newFrame.glowTexture:SetVertexColor(r, g, b, a)
        self:OLDSetVertexColor(r, g, b, a);
        --We save the colors to the texture object, this way we don't need to use GetVertexColor
        self.r = r or 1;
        self.g = g or 1;
        self.b = b or 1;
        self.a = a or 1;
    end

    --We save the colors to the texture object, this way we don't need to use GetVertexColor
    newFrame.glowTexture:SetVertexColor(1, 1, 1, 1);

    newFrame.glowTexture:SetTexture(Questie.icons["glow"])
    newFrame.glow:Hide()
    newFrame.glow:SetPoint("CENTER", -9, -9) -- 2 pixels bigger than normal icon
    newFrame.glow:EnableMouse(false)

    newFrame:SetScript("OnEnter", OnEnter);        --Script Toolip
    newFrame:SetScript("OnLeave", _Qframe.OnLeave) --Script Exit Tooltip
    newFrame:RegisterForClicks("RightButtonUp", "LeftButtonUp")
    newFrame:SetScript("OnClick", _Qframe.OnClick);

    newFrame.GlowUpdate = _Qframe.GlowUpdate
    newFrame.BaseOnUpdate = _Qframe.BaseOnUpdate
    newFrame.BaseOnShow = _Qframe.BaseOnShow
    newFrame.BaseOnHide = _Qframe.BaseOnHide

    newFrame.UpdateTexture = _Qframe.UpdateTexture
    newFrame.Unload = _Qframe.Unload

    -- functions for fake hide/unhide
    newFrame.FadeOut = _Qframe.FadeOut
    newFrame.FadeIn = _Qframe.FadeIn
    newFrame.FakeHide = _Qframe.FakeHide
    newFrame.FakeShow = _Qframe.FakeShow
    newFrame.OnShow = _Qframe.OnShow
    newFrame.OnHide = _Qframe.OnHide
    newFrame.ShouldBeHidden = _Qframe.ShouldBeHidden

    newFrame.data = nil
    newFrame:Hide()

    return newFrame
end

function _Qframe:OnLeave()
    if WorldMapTooltip then
        WorldMapTooltip:Hide()
        WorldMapTooltip._rebuild = nil
    end
    if GameTooltip then
        GameTooltip:Hide()
        GameTooltip._Rebuild = nil
    end

    --Reset highlighting if it exists.
    if self.data.lineFrames then
        for _, lineFrame in pairs(self.data.lineFrames) do
            local line = lineFrame.line
            line:SetColorTexture(line.dR, line.dG, line.dB, line.dA)
        end
    end

    if self.data.touchedPins then
        for i = #self.data.touchedPins, 1, -1 do
            local entry = self.data.touchedPins[i]
            local icon = entry.icon;
            icon.texture:SetVertexColor(unpack(entry.color));
        end
        self.data.touchedPins = nil;
    end
    GameTooltip.ShownAsMapIcon = false

    if QuestieCompat.Is335 then QuestieCompat.SetupTooltip(self, true) end
end

function _Qframe:OnClick(button)
    if self and self.UiMapID and WorldMapFrame and WorldMapFrame:IsShown() and not IsModifierKeyDown() and not self.miniMapIcon then
        if button == "RightButton" then
            local currentMapParent = WorldMapFrame:GetMapID()
            if currentMapParent then
                local mapInfo = C_Map.GetMapInfo(currentMapParent)
                currentMapParent = mapInfo.parentMapID

                if currentMapParent and currentMapParent > 0 then
                    WorldMapFrame:SetMapID(currentMapParent)
                end
            end
        else
            if self.UiMapID ~= WorldMapFrame:GetMapID() then
                WorldMapFrame:SetMapID(self.UiMapID);
            end
        end
    else
        -- This will work in either the WorldMapFrame or the MiniMapFrame as long as there is an icon
        if self and self.UiMapID and button == "LeftButton" then
            if (not ChatEdit_GetActiveWindow()) then
                if self.data.Type == "available" and IsShiftKeyDown() then
                    StaticPopupDialogs["QUESTIE_CONFIRMHIDE"]:SetQuest(self.data.Id)
                    StaticPopup_Show("QUESTIE_CONFIRMHIDE")
                elseif self.data.Type == "manual" and IsShiftKeyDown() and not self.data.ManualTooltipData.disableShiftToRemove then
                    QuestieMap:UnloadManualFrames(self.data.id)
                end
            else
                ChatEdit_InsertLink(QuestieLink:GetQuestLinkString(self.data.QuestData.level, self.data.QuestData.name, self.data.Id))
            end
        end
    end

    -- TomTom integration
    if self and self.UiMapID and IsControlKeyDown() and TomTom and TomTom.AddWaypoint then
        local m = self.UiMapID
        local x = self.x / 100
        local y = self.y / 100
        local title = self.data.Name
        local add = true

        -- Remove old waypoint if set
        if Questie.db.char._tom_waypoint and TomTom.RemoveWaypoint then
            local waypoint = Questie.db.char._tom_waypoint
            TomTom:RemoveWaypoint(waypoint)
            add = QuestieCompat.Is335 or (waypoint[1] ~= m or waypoint[2] ~= x or waypoint[3] ~= y or waypoint.title ~= title or waypoint.from ~= "Questie")
        end

        -- Add waypoint
        if QuestieCompat.Is335 then
            Questie.db.char._tom_waypoint = QuestieCompat.TomTom_AddWaypoint(title, m, self.x, self.y)
        else
            Questie.db.char._tom_waypoint = add and TomTom:AddWaypoint(m, x, y, { title = title, crazy = true, from = "Questie" })
        end
    end

    -- Make sure we don't break the map ping feature - this allows us to ping our own icons.
    if self.miniMapIcon and button == "RightButton" and not IsModifierKeyDown() then
        local _, _, _, x, y = self:GetPoint()
        Minimap:PingLocation(x, y)
    end
end

function _Qframe:GlowUpdate()
    if self.glow and self.glow.IsShown and self.glow:IsShown() then
        --Due to this always being 1:1 we can assume that if one isn't correct, the other isn't either
        --We can also assume that both change at the same time so we only check one.
        if (self.glow:GetWidth() ~= self:GetWidth() * 1.13) then ---self.glow:GetHeight() ~= self:GetHeight() * 1.13
            self.glow:SetSize(self:GetWidth() * 1.13, self:GetHeight() * 1.13)
            self.glow:SetPoint("CENTER", self, 0, 0)
        end
        if self.data and self.data.ObjectiveData and self.data.ObjectiveData.Color and self.glowTexture then
            --Due to us now saving the alpha inside of the texture we don't need to check the main texture anymore.
            --The question is is it faster to get and compare or just set straight up?
            if (self.glowTexture.r ~= self.data.ObjectiveData.Color[1] or self.glowTexture.g ~= self.data.ObjectiveData.Color[2] or self.glowTexture.b ~= self.data.ObjectiveData.Color[3] or self.texture.a ~= self.glowTexture.a) then
                self.glowTexture:SetVertexColor(self.data.ObjectiveData.Color[1], self.data.ObjectiveData.Color[2], self.data.ObjectiveData.Color[3], self.texture.a or 1)
            end
        end
    end
end

function _Qframe:BaseOnShow()
    local data = self.data

    if data and data.Type and data.Type == "complete" then
        self:SetFrameLevel(self:GetFrameLevel() + 1)
    end
    if ((self.miniMapIcon and Questie.db.profile.alwaysGlowMinimap) or ((not self.miniMapIcon) and Questie.db.profile.alwaysGlowMap)) and
        data and data.ObjectiveData and
        data.ObjectiveData.Color and
        (data.Type and (data.Type ~= "available" and data.Type ~= "complete")
        ) then
        self.glow:SetWidth(self:GetWidth() * 1.13)
        self.glow:SetHeight(self:GetHeight() * 1.13)
        self.glow:SetPoint("CENTER", self, 0, 0)
        local _, _, _, alpha = self.texture:GetVertexColor()
        self.glowTexture:SetVertexColor(data.ObjectiveData.Color[1], data.ObjectiveData.Color[2], data.ObjectiveData.Color[3], alpha or 1)
        self.glow:Show()
        local frameLevel = self:GetFrameLevel()
        if frameLevel > 0 then
            self.glow:SetFrameLevel(frameLevel - 1)
        end
    end
end

function _Qframe:BaseOnHide()
    self.glow:Hide()
end

function _Qframe:UpdateTexture(texture)
    --Different settings depending on noteType
    local globalScale
    local objectiveColor
    local alpha

    if (self.miniMapIcon) then
        globalScale = Questie.db.profile.globalMiniMapScale;
        objectiveColor = Questie.db.profile.questMinimapObjectiveColors;
        alpha = 0;
    else
        globalScale = Questie.db.profile.globalScale;
        objectiveColor = Questie.db.profile.questObjectiveColors;
        alpha = 1;
    end

    self.texture:SetTexture(texture)
    --self.data.Icon = texture;
    local colors = { 1, 1, 1 }

    if self.data.IconColor ~= nil and objectiveColor then
        colors = self.data.IconColor
    end
    self.texture:SetVertexColor(colors[1], colors[2], colors[3], alpha);

    if self.data.IconScale then
        local scale = 16 * ((self.data:GetIconScale() or 1) * (globalScale or 0.7));
        self:SetWidth(scale)
        self:SetHeight(scale)
    else
        self:SetWidth(16)
        self:SetHeight(16)
    end
end

function _Qframe:Unload()
    if not self._loaded then
        self._needsUnload = true
        return -- icon is still in the draw queue
    end
    self._needsUnload = nil
    self._loaded = nil
    --Questie:Debug(Questie.DEBUG_SPAM, "[_Qframe:Unload]")
    self:SetScript("OnUpdate", nil)
    self:SetScript("OnShow", nil)
    self:SetScript("OnHide", nil)
    self:SetFrameStrata("FULLSCREEN");
    self:SetFrameLevel(0);

    -- Reset questIdFrames so they won't be toggled again
    local frameName = self:GetName()
    if frameName and self.data.Id and QuestieMap.questIdFrames[self.data.Id] and QuestieMap.questIdFrames[self.data.Id][frameName] then
        QuestieMap.questIdFrames[self.data.Id][frameName] = nil
    end

    --We are reseting the frames, making sure that no data is wrong.
    if self ~= nil and self.hidden and self._show ~= nil and self._hide ~= nil then -- restore state to normal (toggle questie)
        self.hidden = false
        self.Show = self._show;
        self.Hide = self._hide;
        self._show = nil
        self._hide = nil
    end
    self.shouldBeShowing = nil
    self.faded = nil
    HBDPins:RemoveMinimapIcon(Questie, self)
    HBDPins:RemoveWorldMapIcon(Questie, self)
    QuestieDBMIntegration:UnregisterHudQuestIcon(tostring(self))

    if (self.texture) then
        self.texture:SetVertexColor(1, 1, 1, 1)
    end
    self.miniMapIcon = nil;
    self:SetScript("OnUpdate", nil)

    if self.fadeLogicTimer then
        self.fadeLogicTimer:Cancel();
    end
    if self.glowLogicTimer then
        self.glowLogicTimer:Cancel();
    end
    --Unload potential waypoint frames that are used for pathing.
    if self.data and self.data.lineFrames then
        for _, lineFrame in pairs(self.data.lineFrames) do
            lineFrame:Unload();
        end
    end

    if self.OnHide then self:OnHide() end -- the event might trigger after OnHide=nil even if its set after self:Hide()
    self:Hide()
    self.glow:Hide()
    self.data = nil -- Just to be safe
    self.x = nil
    self.y = nil
    self.AreaID = nil
    self.UiMapID = nil
    self.lastGlowFade = nil
    self.worldX = nil
    self.worldY = nil
    QuestieFramePool:RecycleFrame(self)
end

function _Qframe:FadeOut()
    if not self.faded then
        self.faded = true
        if self.texture then
            local r, g, b = self.texture:GetVertexColor()
            self.texture:SetVertexColor(r, g, b, Questie.db.profile.iconFadeLevel)
        end
        if self.glowTexture then
            local r, g, b = self.glowTexture:GetVertexColor()
            self.glowTexture:SetVertexColor(r, g, b, Questie.db.profile.iconFadeLevel)
        end
        if self.data.lineFrames then
            for _, lineFrame in pairs(self.data.lineFrames) do
                local line = lineFrame.line
                if line then
                    line:SetColorTexture(line.dR, line.dG, line.dB, Questie.db.global.iconFadeLevel)
                end
            end
        end
    end
end

function _Qframe:FadeIn()
    if self.faded then
        self.faded = nil
        if self.texture then
            local r, g, b = self.texture:GetVertexColor()
            self.texture:SetVertexColor(r, g, b, 1)
        end
        if self.glowTexture then
            local r, g, b = self.glowTexture:GetVertexColor()
            self.glowTexture:SetVertexColor(r, g, b, 1)
        end
        if self.data.lineFrames then
            for _, lineFrame in pairs(self.data.lineFrames) do
                local line = lineFrame.line
                if line then
                    line:SetColorTexture(line.dR, line.dG, line.dB, line.dA)
                end
            end
        end
    end
end

--- This is needed because HBD will show the icons again after switching zones and stuff like that
function _Qframe:FakeHide()
    if not self.hidden then
        self.shouldBeShowing = self:IsShown();
        self._show = self.Show;
        self.Show = function()
            self.shouldBeShowing = true;
        end
        self:Hide();
        if self.data.lineFrames then
            for _, line in pairs(self.data.lineFrames) do
                line:Hide()
            end
        end
        self._hide = self.Hide;
        self.Hide = function()
            self.shouldBeShowing = false;
        end
        self.hidden = true
    end
end

--- This is needed because HBD will show the icons again after switching zones and stuff like that
function _Qframe:FakeShow()
    if self.hidden then
        self.hidden = false
        self.Show = self._show;
        self.Hide = self._hide;
        self._show = nil
        self._hide = nil
        if self.shouldBeShowing then
            self:Show();
            if self.data.lineFrames then
                for _, line in pairs(self.data.lineFrames) do
                    line:Show()
                end
            end
        end
    end
end

---Checks wheather the frame/icon should be hidden or not. Only for quest icons/frames.
---@return boolean @True if the frame/icon should be hidden and :FakeHide() should be called, false otherwise
function _Qframe:ShouldBeHidden()
    local profile = Questie.db.profile
    local data = self.data
    local iconType = data.Type -- v6.5.1 values: available, complete, manual, monster, object, item, event. This function is not called with manual.
    local questId = data.Id

    --investigate quest and cache results to minimize DB lookups
    local repeatable = QuestieDB.IsRepeatable(questId)
    local event = QuestieDB.IsActiveEventQuest(questId)
    local dungeon = QuestieDB.IsDungeonQuest(questId)
    local raid = QuestieDB.IsRaidQuest(questId)
    local pvp = QuestieDB.IsPvPQuest(questId)
    local normal = not (repeatable or event or dungeon or raid or pvp)

    if (not profile.enabled) -- all quest icons disabled
        or ((not profile.enableMapIcons) and (not self.miniMapIcon))
        or ((not profile.enableMiniMapIcons) and (self.miniMapIcon))
        or ((not profile.enableTurnins) and iconType == "complete")
        or ((not profile.enableObjectives) and (iconType == "monster" or iconType == "object" or iconType == "event" or iconType == "item"))
        or (profile.hideUnexploredMapIcons and not QuestieMap.utils:IsExplored(self.UiMapID, self.x, self.y)) -- Hides unexplored map icons
        or (profile.hideUntrackedQuestsMapIcons and not QuestieQuest:ShouldShowQuestNotes(questId))           -- Hides untracked map icons
        or (data.ObjectiveData and data.ObjectiveData.HideIcons)
        or (data.QuestData and data.QuestData.HideIcons and iconType ~= "complete")
        -- Hide only available quest icons of following quests. I.e. show objectives and complete icons always (when they are in questlog).
        -- i.e. (iconType == "available")  ==  (iconType ~= "monster" and iconType ~= "object" and iconType ~= "event" and iconType ~= "item" and iconType ~= "complete"):
        or (iconType == "available"
            and ((not DailyQuests:IsActiveDailyQuest(questId)) -- hide not-today-dailies
                or ((not profile.enableAvailable) and normal)
                or ((not profile.showRepeatableQuests) and repeatable)
                or ((not profile.showEventQuests) and event)
                or ((not profile.showDungeonQuests) and dungeon)
                or ((not profile.showRaidQuests) and raid)
                or ((not profile.showPvPQuests) and pvp)
            -- this quest group isn't loaded at all while disabled:
            -- or ((not questieCharDB.showAQWarEffortQuests) and QuestieQuestBlacklist.AQWarEffortQuests[questId])
            )
        )
    then
        return true
    end

    return false
end
